// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CC_RESOURCES_RESOURCE_POOL_H_
#define CC_RESOURCES_RESOURCE_POOL_H_

#include <stddef.h>
#include <stdint.h>

#include <deque>
#include <map>
#include <memory>

#include "base/macros.h"
#include "base/memory/memory_coordinator_client.h"
#include "base/memory/ptr_util.h"
#include "base/trace_event/memory_dump_provider.h"
#include "cc/base/cc_export.h"
#include "cc/resources/resource.h"
#include "cc/resources/resource_format.h"
#include "cc/resources/scoped_resource.h"

namespace cc {

class CC_EXPORT ResourcePool : public base::trace_event::MemoryDumpProvider,
                               public base::MemoryCoordinatorClient {
public:
    // Delay before a resource is considered expired.
    static base::TimeDelta kDefaultExpirationDelay;

    static std::unique_ptr<ResourcePool> CreateForGpuMemoryBufferResources(
        ResourceProvider* resource_provider,
        base::SingleThreadTaskRunner* task_runner,
        gfx::BufferUsage usage,
        const base::TimeDelta& expiration_delay)
    {
        return base::WrapUnique(new ResourcePool(resource_provider, task_runner,
            usage, expiration_delay));
    }

    static std::unique_ptr<ResourcePool> Create(
        ResourceProvider* resource_provider,
        base::SingleThreadTaskRunner* task_runner,
        ResourceProvider::TextureHint hint,
        const base::TimeDelta& expiration_delay)
    {
        return base::WrapUnique(new ResourcePool(resource_provider, task_runner,
            hint, expiration_delay));
    }

    ~ResourcePool() override;

    // Tries to reuse a resource. If none are available, makes a new one.
    Resource* AcquireResource(const gfx::Size& size,
        ResourceFormat format,
        const gfx::ColorSpace& color_space);

    // Tries to acquire the resource with |previous_content_id| for us in partial
    // raster. If successful, this function will retun the invalidated rect which
    // must be re-rastered in |total_invalidated_rect|.
    Resource* TryAcquireResourceForPartialRaster(
        uint64_t new_content_id,
        const gfx::Rect& new_invalidated_rect,
        uint64_t previous_content_id,
        gfx::Rect* total_invalidated_rect);

    // Called when a resource's content has been fully replaced (and is completely
    // valid). Updates the resource's content ID to its new value.
    void OnContentReplaced(ResourceId resource_id, uint64_t content_id);
    void ReleaseResource(Resource* resource);

    void SetResourceUsageLimits(size_t max_memory_usage_bytes,
        size_t max_resource_count);
    void ReduceResourceUsage();
    bool ResourceUsageTooHigh();

    // Must be called regularly to move resources from the busy pool to the unused
    // pool.
    void CheckBusyResources();

    size_t memory_usage_bytes() const { return in_use_memory_usage_bytes_; }
    size_t resource_count() const { return in_use_resources_.size(); }

    // Overridden from base::trace_event::MemoryDumpProvider:
    bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
        base::trace_event::ProcessMemoryDump* pmd) override;

    // Overriden from base::MemoryCoordinatorClient.
    void OnMemoryStateChange(base::MemoryState state) override;

    size_t GetTotalMemoryUsageForTesting() const
    {
        return total_memory_usage_bytes_;
    }
    size_t GetTotalResourceCountForTesting() const
    {
        return total_resource_count_;
    }
    size_t GetBusyResourceCountForTesting() const
    {
        return busy_resources_.size();
    }

protected:
    // Constructor for creating GPU memory buffer resources.
    ResourcePool(ResourceProvider* resource_provider,
        base::SingleThreadTaskRunner* task_runner,
        gfx::BufferUsage usage,
        const base::TimeDelta& expiration_delay);

    // Constructor for creating standard resources.
    ResourcePool(ResourceProvider* resource_provider,
        base::SingleThreadTaskRunner* task_runner,
        ResourceProvider::TextureHint hint,
        const base::TimeDelta& expiration_delay);

private:
    FRIEND_TEST_ALL_PREFIXES(ResourcePoolTest, ReuseResource);
    class PoolResource : public ScopedResource {
    public:
        static std::unique_ptr<PoolResource> Create(
            ResourceProvider* resource_provider)
        {
            return base::WrapUnique(new PoolResource(resource_provider));
        }
        void OnMemoryDump(base::trace_event::ProcessMemoryDump* pmd,
            const ResourceProvider* resource_provider,
            bool is_free) const;

        uint64_t content_id() const { return content_id_; }
        void set_content_id(uint64_t content_id) { content_id_ = content_id; }

        base::TimeTicks last_usage() const { return last_usage_; }
        void set_last_usage(base::TimeTicks time) { last_usage_ = time; }

        gfx::Rect invalidated_rect() const { return invalidated_rect_; }
        void set_invalidated_rect(const gfx::Rect& invalidated_rect)
        {
            invalidated_rect_ = invalidated_rect;
        }

    private:
        explicit PoolResource(ResourceProvider* resource_provider)
            : ScopedResource(resource_provider)
            , content_id_(0)
        {
        }
        uint64_t content_id_;
        base::TimeTicks last_usage_;
        gfx::Rect invalidated_rect_;
    };

    // Tries to reuse a resource. Returns |nullptr| if none are available.
    Resource* ReuseResource(const gfx::Size& size,
        ResourceFormat format,
        const gfx::ColorSpace& color_space);

    // Creates a new resource without trying to reuse an old one.
    Resource* CreateResource(const gfx::Size& size,
        ResourceFormat format,
        const gfx::ColorSpace& color_space);

    void DidFinishUsingResource(std::unique_ptr<PoolResource> resource);
    void DeleteResource(std::unique_ptr<PoolResource> resource);
    static void UpdateResourceContentIdAndInvalidation(
        PoolResource* resource,
        uint64_t new_content_id,
        const gfx::Rect& new_invalidated_rect);

    // Functions which manage periodic eviction of expired resources.
    void ScheduleEvictExpiredResourcesIn(base::TimeDelta time_from_now);
    void EvictExpiredResources();
    void EvictResourcesNotUsedSince(base::TimeTicks time_limit);
    bool HasEvictableResources() const;
    base::TimeTicks GetUsageTimeForLRUResource() const;

    ResourceProvider* resource_provider_ = nullptr;
    bool use_gpu_memory_buffers_ = false;
    gfx::BufferUsage usage_ = gfx::BufferUsage::GPU_READ_CPU_READ_WRITE;
    ResourceProvider::TextureHint hint_ = ResourceProvider::TEXTURE_HINT_DEFAULT;
    size_t max_memory_usage_bytes_ = 0;
    size_t max_resource_count_ = 0;
    size_t in_use_memory_usage_bytes_ = 0;
    size_t total_memory_usage_bytes_ = 0;
    size_t total_resource_count_ = 0;

    // Holds most recently used resources at the front of the queue.
    using ResourceDeque = std::deque<std::unique_ptr<PoolResource>>;
    ResourceDeque unused_resources_;
    ResourceDeque busy_resources_;

    using InUseResourceMap = std::map<ResourceId, std::unique_ptr<PoolResource>>;
    InUseResourceMap in_use_resources_;

    scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
    bool evict_expired_resources_pending_ = false;
    const base::TimeDelta resource_expiration_delay_;

    base::WeakPtrFactory<ResourcePool> weak_ptr_factory_;

    DISALLOW_COPY_AND_ASSIGN(ResourcePool);
};

} // namespace cc

#endif // CC_RESOURCES_RESOURCE_POOL_H_
